% author: Starrysky
% last update date: 2022-10-09
% description: 以 UTF-8 编码保存, 使用 XeLaTeX 编译

% 使用CTex宏集以支持中文
\documentclass[UTF8]{ctexart}
% 从此处到正文开始前是导言区, 进行文档全局设置
% 设置页面大小和页边距
\usepackage[a4paper, margin=1in, bottom=0.5in]{geometry}
% 支持数学公式
\usepackage{amsmath}
% 支持超链接
\usepackage{hyperref}
\hypersetup{
	colorlinks=true,
	linkcolor=black,      
	urlcolor=blue,
	pdftitle={Overleaf Example},
	pdfpagemode=FullScreen,
}
% 支持图片
\usepackage{graphicx}
% 设置图片文件目录
\graphicspath{{./resources}}
% 支持伪代码
\usepackage{algorithm}
\usepackage{algorithmic}

\usepackage{caption}
\usepackage[dvipsnames]{xcolor}  % 更全的色系
\usepackage{listings}  % 排代码用的宏包
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% listings设置
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\lstset{
	language = Go,
	backgroundcolor = \color{yellow!10},    % 背景色：淡黄
	basicstyle = \small\ttfamily,           % 基本样式 + 小号字体
	rulesepcolor= \color{gray},             % 代码块边框颜色
	breaklines = true,                  % 代码过长则换行
	numbers = left,                     % 行号在左侧显示
	numberstyle = \small,               % 行号字体
	keywordstyle = \color{blue},            % 关键字颜色
	commentstyle =\color{Green},        % 注释颜色
	stringstyle = \color{red!100},          % 字符串颜色
	frame = shadowbox,                  % 用（带影子效果）方框框住代码块
	showspaces = false,                 % 不显示空格
	columns = fixed,                    % 字间距固定	
	%escapeinside={<@}{@>}              % 特殊自定分隔符：<@可以自己加颜色@>
	morekeywords = {as},                % 自加新的关键字（必须前后都是空格）
	deletendkeywords = {compile}        % 删除内定关键字；删除错误标记的关键字用deletekeywords删！
}
% 替换为Input和Output
\renewcommand{\algorithmicrequire}{\textbf{Input:}}
\renewcommand{\algorithmicensure}{\textbf{Output:}}

\title{RPC上机作业 \quad 书面报告}
\author{小组成员(姓名拼音顺序): \quad 方仕元\;刘畅\;谢科屹\;周帅 \;祝弘华}
\date{2022年12月15日}
% 正文部分
\begin{document}

% 显示导言区设置的title、author、date等信息
\maketitle
% 显示目录，目录要编译两次
\tableofcontents
% 分页
\newpage

% 多级section、paragraph的排版
\section{预备知识与开发环境}
本节主要介绍了实际开发程序前必要的学习内容和其它准备工作，主要包括以下几个方面:

\begin{enumerate}
	\item 第三方RPC库的选择，go语言内建的RPC包的优势
	\item RPC包背后的实现原理
	\item RPC包的使用方法
	\item 开发环境
\end{enumerate}

\subsection{第三方RPC库的选择}
RPC(remote procedure call)是指远程过程调用。常见的应用场景如下: 设有客户端A与服务器B，其中服务器上B部署一个应用，客户端A上的应用想调用服务器B上的应用提供的接口；由于不在一个内存空间，不能直接调用，所以需要通过网络来传递调用的语义和传达调用的数据。

市面上能提供RPC服务的框架有很多，如gRPC, Dubbo, Spring Cloud等。本次实验我们选择\href{https://pkg.go.dev/net/rpc}{go语言原生的RPC包} ，主要有以下几点考虑:
\begin{enumerate}
	\item go语言作为一种新兴高级编程语言，主打并发与网络通信，特别方便实现服务器-客户端交互模式
	\item go语言编译环境内建RPC包，无需额外安装依赖
	\item go语言的RPC包以Google主导的gRPC为基础，应用前景以及跨平台兼容性较好
\end{enumerate}

综上所述，本文选择在go语言内建RPC包的基础上完成实验。下文中，若无额外说明，涉及的RPC框架均为go语言中的RPC包。

\subsection{RPC包的实现原理}
RPC本质上是一种协议，允许一台计算机程序通过网络从远程计算机程序上请求服务，无需使用者了解底层网络技术。RPC协议构建在TCP、UDP、HTTP之上，在OSI七层网络通信模型中，RPC跨域了传输层和应用层，使网络分布式程序开发更为容易。


RPC采用C/S(客户端/服务器)的工作模式，完整的运行逻辑如下图\ref{fig:RPCmodel}所示:
\begin{figure}[H]
	\centering
	\includegraphics[width = .8\textwidth]{./素材/RPC模型.png}
	\caption{RPC运行大致逻辑}
	\label{fig:RPCmodel}
\end{figure}

请求程序为客户端(client)，服务提供程序为服务端(server)。当执行一个远程过程调用时，客户端程序首先会发送一个带有参数的调用信息服务端，然后等待服务端响应，对应的执行流程如下:
\begin{itemize}
	\item client调用client stub(客户端存根)，本地过程调用，参数以正常方式压入堆栈
	\item  client stub 将参数打包成一个消息m，这个打包过程也称为marshalling(序列化)
	\item  client所在的操作系统将消息m发送给server
   	\item  server所在的操作系统将收到的包m传给server stub(服务器存根)
	\item  server stub解包得到参数。解包也称作unmarshalliing(反序列化)
	\item  server stub执行服务程序并得到计算结果。
	\item  server stub反方向同样步骤将计算结果返回给client
\end{itemize}

综上所述，除了将消息序列化、反序列化之外，其它步骤与一般的服务器--客户端消息通信并无二致。

\subsection{使用方法}
由上节可知，RPC库主要实现的就是序列化和反序列化。幸运的是，go语言RPC包已经内部实现了这个过程，序列化/反序列化过程对用户透明。用户的自定义程序只要满足一定要求，RPC包便能成功实现序列化和反序列化。用户的自定义程序在go语言中一般叫做"方法"，RPC对方法的要求如下:
\begin{itemize}
	\item 方法必须是可导出的，不能是私有类型
	\item 方法涉及的自定义结构体也必须可导出
	\item 方法的形式参数有且仅有2个，二者均为指针类型
	\item 方法的返回类型为error
\end{itemize}

一个标准的自定义方法样例如下:
\begin{lstlisting}
	func (t *T) MethodName(argType *T1, replyType *T2) error
\end{lstlisting}
其中,T,T1,T2均为用户自定义的结构体(struct)。
只要满足了上述要求，RPC包就会自动为用户定义的方法进行序列化与反序列化。

\subsection{开发环境}
本次上机实验的开发环境如下：
\begin{itemize}
	\item 主要编程语言: go-lang
	\item 第三方RPC库: go语言原生包\href{https://pkg.go.dev/net/rpc}{net/rpc}
	\item 操作系统: 内核版本5.4.0-132-generic, 发行版本Ubuntu 18.04.1
	\item 项目编译模式: Makefile
\end{itemize}

\section{算法设计与实现}
\subsection{服务器--客户端模型}
本次实验为方便起见，通过TCP连接搭建服务器--客户端模型。

上文说道，go语言发布之初，主打并发与网络交互；实际使用过程中，程序员只需用类型\emph{net.Conn}模拟一个TCP连接，接着服务器和客户端便可通过\emph{net.Conn}进行通信。接下来讲解\emph{net.Conn}的创建过程。

在服务器端，调用库函数\emph{net.Listen}，使服务器进程绑定端口并进入监听模式。接着，再调用\emph{net.Accept}函数，接受发送到服务器监听端口的TCP连接请求并建立TCP连接\emph{net.Conn}

客户端则更为简单，只需调用一个函数\emph{net.Dial}，便可同时完成随机端口分配、端口绑定、向服务器端口发送TCP连接请求等操作；只要服务器端已调用\emph{net.Accept}，双方便可建立TCP连接。

除了编程方面，在实际应用中，我们希望服务器针对每个客户端进程提供独立服务。这就意味着，服务器端要开启多个进程，每个进程处理一个客户端的TCP连接。在go语言中，使用goroutine可以轻松满足我们对于多进程或者多线程的需求。

综上所述，服务器端和客户端的伪代码分别如下:
\begin{lstlisting}[caption={服务器端伪代码},label={code:服务器端}]
	/* 服务器端 */
	
	// 绑定端口9090并进入监听模式
	listening_state, err := net.Listen("tcp", "127.0.0.1:9090")
	
	for {
		// 接收发送给服务器的请求，并建立TCP连接
		TCP_conn, err:=listening_state.Accept()
		// 开启一个独立的进程goroutine，为客户端服务
		go serve(TCP_conn)
	}
\end{lstlisting}

\begin{lstlisting}[caption={客户端伪代码},label={code:客户端}]
	/* 客户端 */
	TCP_conn, err:=net.Dial("tcp","127.0.0.1:9090")
	// 可以通过TCP_conn与服务器交互
\end{lstlisting}

\subsection{远程锁服务}
服务器与客户端建立TCP连接\emph{TCP\_Conn}之后，便可开始使用RPC服务。

服务器端，需要调用库函数\emph{rpc.RegisterName}注册RPC服务，然后再调用\emph{rpc.ServeConn}即可处理客户端的RPC请求。客户端，先调用库函数\emph{rpc.NewCient}注册一个遵循RPC协议的账户，再调用\emph{rpc.Call}即可发送RPC请求。服务器端和客户端的伪代码分别如下；其中\emph{common.LockService}是锁服务结构体名称，\emph{arguments}是形式参数，包括客户端的身份信息，如端口号、发生时间等。
\begin{lstlisting}[caption={服务器端伪代码}]
	rpc.RegisterName("lockService", new(common.LockService))
	......
	// 与客户端建立TCP连接TCP_conn
	rpc.ServeConn(TCP_conn)
\end{lstlisting}

\begin{lstlisting}[caption={客户端伪代码}]
	...... // 与服务器端建立TCP连接TCP_conn
	client:=rpc.NewClient(TCP_conn)
	
	// 相当于客户端调用lockService.Acquire(arguments)，结果写回result中
	client.Call("lockService.Acquire", arguments, &result)
\end{lstlisting}

接下来是锁服务的设计。上文提到，服务器会开启多个进程分别处理客户端的请求，但是锁作为共享变量，应当保证多进程操作的原子性。go语言本身有操作系统锁\emph{sync.Mutex}，但是go语言官方更推荐用channel来模拟锁。channel类似于操作系统的管道，管道双方可以信息交互，如果不能交互，则会阻滞(blocking); channel可以满足多进程安全性。

\subsection{RPC请求去重}
上文中，客户端请求锁服务时需要准备参数\emph{arguments}，其中包含了客户端的端口号、发生时间等信息。如果要去重，只需在服务器端，将客户端端口号、时间这两种信息，记录在一个哈希表中；然后每次处理客户端请求时，查看这两种信息是否已在哈希表中出现。若出现则说明重复，拒绝处理请求；否则将当前信息加入哈希表并处理。

\section{实验效果}
本节介绍运行效果，运行环境与开发环境一致。
\subsection{锁服务的正确性}
\begin{enumerate}
	\item 锁操作的原子性。
	
	为显示该效果，我们准备一个服务器上的公共变量$total\_count$；同时运行3个客户端，每个客户端进程连续执行$10^6$遍$total\_count++$。一次使用锁服务，另一次不使用，结果如下图\ref{fig:原子性}所示。理论上，变量$total\_count$的值应当为$3\times 10^6$；启用锁服务时，输出值符合理论值; 不启用锁服务时，输出值为2427712(多次实验可以得到不同的随机值)。这说明本文的锁能够满足用户的原子性需求
\begin{figure}[H]
	\centering
	\includegraphics[width = .8\textwidth]{./素材/原子性.jpg}
	\caption{RPC请求\quad 锁操作的原子性, 保护共享变量}
	\label{fig:原子性}
\end{figure}
	

	\item 不可重复加锁且不可重复释放锁，运行效果如下图\ref{fig:不可重复}所示。服务器端在后台运行。客户端端口号51308，先尝试两次上锁，第二次上锁被拒绝；再尝试两次解锁，第二次解锁被拒绝。
\begin{figure}[H]
	\centering
	\includegraphics[width = .8\textwidth]{./素材/重复锁.jpg}
	\caption{禁止重复加锁或解锁}
	\label{fig:不可重复}
\end{figure}
	\item 假设用户A已占用锁，另一个用户B不可释放锁，运行效果如下图\ref{fig:身份验证}所示。客户端46394先成功上锁，另一个客户端46408尝试解锁，被拒绝。
\begin{figure}[H]
	\centering
	\includegraphics[width = .8\textwidth]{./素材/身份解锁.jpg}
	\caption{解锁账户必须与加锁账户一致，不能冒名}
	\label{fig:身份验证}
\end{figure}
\end{enumerate}

\subsection{RPC请求去重}
去重效果如下图\ref{fig:去重}所示。图片展示的是客户端，服务器端在后台运行。
\begin{figure}[H]
	\centering
	\includegraphics[width = .8\textwidth]{./素材/去重.jpg}
	\caption{RPC请求\quad 根据发生时间去重}
	\label{fig:去重}
\end{figure}
客户端端口号为53240，在时刻12:50和13:01分别发送了加锁、解锁调用请求并顺利完成。
然后，客户端再次发送时刻12:50的加锁请求，被服务器拒绝。

\section{总结}
本次上机实验中，我们在go语言RPC包的基础上，模拟了锁服务，并对重复的RPC请求进行去重。我们回顾了课堂上RPC的相关理论基础知识，并实际使用了第三方RPC框架，实践能力得到了锻炼，并且对课程知识也有了更深刻的理解。最后感谢老师的指导，感谢组内各位同学的努力！

%\subsection{Subsection Title}
%todo
%\subsubsection{Subsubsection Title}
%todo
%\paragraph{Paragraph Title}
%todo
%\subparagraph{Subparagraph Title}
%todo
%\subsection{Subsection Title}
%todo
%\paragraph{Paragraph Title}
%todo
%\section{公式}
%首先，$a+b=c$是一个行内公式。下面看一个行间公式:
%\[
%    a^2+b^2=c^2
%\]
%
%
%对公式进行编号
%\begin{equation}
%    a^2+b^2=c^2
%\end{equation}
%
%
%更复杂的公式，可以辅以这个工具：\href{https://www.latexlive.com/}{https://www.latexlive.com/}
%
%
%\section{图片}
%如图\ref{fig:1}所示.
%\begin{figure}[H]
%    \centering
%    \includegraphics[width = .8\textwidth]{./素材/一拳把地球打爆.jpg}
%    \caption{cat figure}
%    \label{fig:1}
%\end{figure}
%
%\section{表格}
%% l、c、r控制对齐方式
%\begin{tabular}{|l|c|r|}
%
%    \hline
%    操作系统       & 发行版      & 编辑器       \\
%    \hline
%    Windows    & MikTeX   & TexMakerX \\
%    \hline
%    Unix/Linux & teTeX    & Kile      \\
%    \hline
%    Mac OS     & MacTeX   & TeXShop   \\
%    \hline
%    通用         & TeX Live & TeXworks  \\
%    \hline
%\end{tabular}
%
%\section{伪代码}
%\begin{algorithm}[H]    
%    \caption{find $n^{th}$ smallest  value from 2n values in two separate databases}
%    \label{algo:1}
%    \textbf{function}  \textbf{FindMidianValue}($DB_a,DB_b,k,head_a,head_b$) 
%    \begin{algorithmic}[1]
%        \IF{$k == 1$}
%            \RETURN $min(query(DB_a, head_a), query(DB_b, head_b))$
%        \ENDIF
%        \STATE $k_a \gets \lfloor \frac{k}{2} \rfloor $
%        \STATE $k_b \gets k - a $
%        \STATE $v_a\gets query(DB_a, head_a+k_a-1)$
%        \STATE $v_b\gets query(DB_b, head_b+k_b-1)$
%        \IF{$v_a<v_b$}
%            \RETURN \textbf{FindMidianValue}($DB_a,DB_b,k-k_a,head_a+k_a,head_b$)
%        \ELSE
%            \RETURN \textbf{FindMidianValue}($DB_a,DB_b,k-k_b,head_a,head_b+k_b$)
%        \ENDIF
%    \end{algorithmic}
%\end{algorithm}
\end{document}